package tlog

import (
	"context"
	"errors"
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type traceLogger struct {
	logger           *zap.Logger
	Level            zap.AtomicLevel
	traceIDExtractor func(ctx context.Context) string // traceID extractor
}

var (
	defaultLog *traceLogger
	stdlog     *log.Logger
)

var defaultTraceIDExtractor = func(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	v := ctx.Value("traceID")
	if s, ok := v.(string); ok {
		return s
	}
	return ""
}

// InitLogCommon 常用配置，等效于
//
//		processName := filepath.Base(os.Args[0])
//	 tlog.InitLog(tlog.ProcessName(processName),
//	  	tlog.SetTarget("asyncfile"),
//	 	tlog.SetEncode("tjson"),
//			tlog.LogFilePath("/data/log/"+processName))
//
// 需要更多参数，可以在 more 字段添加(注：相同 LogOption，后面的会覆盖前面的）
func InitLogCommon(more ...LogOption) error {
	processName := filepath.Base(os.Args[0])
	logPath := "/data/log/"
	if runtime.GOOS == "windows" {
		logPath = "./"
	}
	logPath += processName
	options := []LogOption{ProcessName(processName), SetTarget("asyncfile"), SetEncode("tjson"), LogFilePath(logPath)}
	options = append(options, more...)
	return InitLog(options...)
}

// InitLog 根据options的设置,初始化日志系统(注：相同 LogOption，后面的会覆盖前面的）
// Example :
//
//		tlog.InitLog(tlog.ProcessName("udbapp_xxx"),
//	    	tlog.SetTarget("asyncfile"),
//	   	tlog.SetEncode("tjson"),
//	  	tlog.LogFilePath("../log/"))
func InitLog(options ...LogOption) error {
	var (
		err    error
		logger *zap.Logger
		level  zap.AtomicLevel
	)
	config := defaultLogOptions
	for _, option := range options {
		option.apply(&config)
	}

	var logfilename = config.logFilePath + "/" + config.processName
	if config.clearHistoryLog {
		// clear old log file
		rerr := os.Remove(logfilename)
		fmt.Println("clear history log file", logfilename, rerr)
	}

	if level, logger, err = zapLogInit(&config); err != nil {
		fmt.Printf("ZapLogInit err:%v", err)
		return err
	}

	logger = logger.WithOptions(zap.AddCallerSkip(2))
	if defaultLog == nil {
		defaultLog = &traceLogger{logger, level, config.traceIDExtractor}
	} else {
		defaultLog.logger = logger
		defaultLog.Level = level
		defaultLog.traceIDExtractor = config.traceIDExtractor
	}
	// redirect go log to defaultLog.logger
	stdlogger := defaultLog.logger.WithOptions(zap.AddCallerSkip(-2))
	zap.RedirectStdLog(stdlogger)
	stdlog = zap.NewStdLog(stdlogger)

	return nil
}

// GetLogger 获取日志对象，一般用于注入第三方库
func GetLogger() *traceLogger {
	return defaultLog
}

// GetStdLogger 获取标准的 log.Logger 对象，用于注入第三方库
func GetStdLogger() *log.Logger {
	return stdlog
}

// GetZLog 获取底层的 zap.Logger 对象（共享输出对象），用于业务二次封装
func (l *traceLogger) GetZLog(opts ...zap.Option) *zap.Logger {
	return l.logger.WithOptions(opts...)
}

// Clone 复制tloger 对象，用于业务二次封装
// Example : GetLogger().Clone(zap.AddCallerSkip(2))
func (l *traceLogger) Clone(opts ...zap.Option) *traceLogger {
	nl := &traceLogger{
		logger:           l.logger,
		Level:            l.Level,
		traceIDExtractor: l.traceIDExtractor,
	}

	nl.logger = l.logger.WithOptions(opts...)
	return nl
}

func (l *traceLogger) getTraceID(ctx context.Context) string {
	if l.traceIDExtractor != nil {
		return l.traceIDExtractor(ctx)
	}
	return ""
}

// Write  实现 io.Writer
func (l *traceLogger) Write(ctx context.Context, p []byte) (n int, err error) {
	l.writelog(ctx, "info", string(p))
	return len(p), nil
}

// Log 实现 github.com/go-log/log.logger 接口
func (l *traceLogger) Log(ctx context.Context, v ...interface{}) {
	msg := fmt.Sprint(v...)
	l.writelog(ctx, "info", msg)
}

// Logf 实现 github.com/go-log/log.logger 接口
func (l *traceLogger) Logf(ctx context.Context, format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	l.writelog(ctx, "info", msg)
}

// Error  输出error级别 的日志
func (l *traceLogger) Error(ctx context.Context, msg string) {
	l.writelog(ctx, "error", msg)
}

// Infof  输出info 级别日志
func (l *traceLogger) Infof(ctx context.Context, format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	l.writelog(ctx, "info", msg)
}

func (l *traceLogger) writelog(ctx context.Context, level, msg string, fields ...zapcore.Field) {
	traceID := l.getTraceID(ctx)
	if len(traceID) > 0 {
		fields = append(fields, zap.String("traceID", traceID))
	}
	switch level {
	case "info":
		l.logger.Info(msg, fields...)
	case "debug":
		l.logger.Debug(msg, fields...)
	case "warn":
		l.logger.Warn(msg, fields...)
	case "error":
		l.logger.Error(msg, fields...)
	case "panic":
		l.logger.Panic(msg, fields...)
	case "dpanic":
		l.logger.DPanic(msg, fields...)
	case "fatal":
		// fatal 是写完日志立刻 os.Exit, 不会调用任何 defer tlog.Sync()，导致异步日志不落盘
		// 这里先 Sync() 一次，然后改成同步模式写对应的日志，这样就不会丢
		l.logger.Sync()
		fcore, ok := l.logger.Core().(*filecore)
		if ok {
			fcore.logfile.SetUseCache(false)
			// fatal 后理论上不执行这个 defer
			defer fcore.logfile.SetUseCache(true)
		}
		l.logger.Fatal(msg, fields...)
	default:
		l.logger.Info(msg, fields...)
	}
}

// Log 设置不同日志级别的日志
// level 日志级别: debug info warn error panic fatal
func Log(ctx context.Context, level string, v ...interface{}) {
	msg := fmt.Sprint(v...)
	defaultLog.writelog(ctx, level, msg)
}

// LogF 设置不同日志级别的日志, 支持自定义format
// level 日志级别: debug info warn error panic fatal
func LogF(ctx context.Context, level string, format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)

	defaultLog.writelog(ctx, level, msg)
}

// Debug 设置debug 级别的日志 .
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Debug(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "debug", msg, fields...)
}

// Info 设置 info 级别的日志 .
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Info(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "info", msg, fields...)
}

// Warn 设置 warn 级别的日志 .
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Warn(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "warn", msg, fields...)
}

// Error 设置 error 级别的日志 .
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Error(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "error", msg, fields...)
}

// DPanic 设置 dpanic 级别的日志 . 在开发模式下，触发panic.
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func DPanic(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "dpanic", msg, fields...)
}

// Panic 设置 panic 级别的日志 . 输出日志后，触发panic.
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Panic(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "panic", msg, fields...)
}

// Fatal 设置 fatal级别的日志 . 输出日志后，触发 os.Exit(1).
//
//	msg 日志关键描述信息
//	fields 由k-v组成的日志信息集合
func Fatal(ctx context.Context, msg string, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "fatal", msg, fields...)
}

// CloudLog 输出 关键信息为 CloudLog 的日志记录,日志级别为info. 目的是便于日志云进行统一的采集.
//
//	fields 由k-v组成的日志信息集合
func CloudLog(ctx context.Context, fields ...zapcore.Field) {
	defaultLog.writelog(ctx, "info", "CloudLog", fields...)
}

// Sync 手工触发日志模块sync
func Sync() error {
	return defaultLog.logger.Sync()
}

// SetLogLevel 设置全局日志模块的 可输出级别
// level : debug(all) info warn error fatal(off,none)
func SetLogLevel(level string) error {
	switch strings.ToLower(level) {
	case "debug", "info", "warn", "error", "fatal":
		level = strings.ToLower(level)
	case "all":
		level = "debug"
	case "off", "none":
		level = "fatal"
	default:
		return errors.New("not support level")
	}

	if defaultLog != nil {
		defaultLog.Level.UnmarshalText([]byte(level))
	}

	return nil
}
